package org.activequant.util.ib.marketscanners;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.activequant.core.domainmodel.marketscanner.InstrumentSpecificationDetails;
import org.activequant.core.domainmodel.marketscanner.MarketScan;
import org.activequant.core.domainmodel.marketscanner.MarketScanChange;
import org.activequant.core.domainmodel.marketscanner.MarketScanChange.ChangeType;
import org.activequant.core.domainmodel.marketscanner.MarketScanRow;
import org.activequant.core.types.Triple;
import org.activequant.util.pattern.events.IEventListener;
/**
 * Holds the following associated variables:
 * <ul>
 * <li>listener(IEventListener&lt;MarketScanChange&gt;)</li>
 * <li>magnitudeRangeStart(double)</li>
 * <li>magnitudeRangeEnd(double)</li>
 * </ul>
 * @author Dimitar
 *
 */
public class MarketScanChangeConvertor {
	/**
	 * private final IEventListener&lt;MarketScanChange&gt; listener;
	 */
	private final IEventListener<MarketScanChange> listener;
	/**
	 * private double magnitudeRangeStart = 0.1d;
	 */
	private double magnitudeRangeStart = 0.1d;// 0 to 1 value should be learned
	// per instrument
	/**
	 * private double magnitudeRangeEnd = Double.MAX_VALUE;
	 */
	private double magnitudeRangeEnd = Double.MAX_VALUE;
	/**
	 * Constructs a MarketScanChangeConvertor using the given listener(IEventListener&lt;MarketScanChange&gt;) to set its associated listener(IEventListener&lt;MarketScanChange&gt;)
	 * @param listener
	 */
	public MarketScanChangeConvertor(IEventListener<MarketScanChange> listener) {
		this.listener = listener;
	}

	public void processNewMarketScan(MarketScan previousScan, MarketScan newScan) {
		// don't do anything if we aren't initialized yet
		if (previousScan == null || newScan == null) {
			return;
		}
		List<MarketScanRow> prevRows = previousScan.getRows();
		List<MarketScanRow> newRows = newScan.getRows();

		// find all MarketScanRow's in the new scan that didn't exist in the
		// previous one
		List<MarketScanRow> newMarketScanRows = new ArrayList<MarketScanRow>(); //weren't in the previous scan but are in the new scan
		newMarketScanRows.addAll(newRows);
		newMarketScanRows.removeAll(prevRows);

		// find all MarketScanRow's in the previous scan that don't exist in the
		// new one
		List<MarketScanRow> removedMarketScanRows = new ArrayList<MarketScanRow>(); //were in the previous scan but not in the new scan
		removedMarketScanRows.addAll(prevRows);
		removedMarketScanRows.removeAll(newRows);

		if (newMarketScanRows.size() != 0 || removedMarketScanRows.size() != 0) {
			int numberOfInstruments = newScan.getRows().size();
			long changeInterval = (newScan.getTimeStamp().getNanoseconds() - previousScan.getTimeStamp().getNanoseconds()) / 1000000L;//time between the 2 scans in millisecs 
			Set<InstrumentSpecificationDetails> newOrShiftedInstrumentSpecificationDetails = new HashSet<InstrumentSpecificationDetails>();
			Set<InstrumentSpecificationDetails> removedOrShiftedInstrumentSpecificationDetails = new HashSet<InstrumentSpecificationDetails>();
			for (MarketScanRow newMarketScanRow : newMarketScanRows) {
				newOrShiftedInstrumentSpecificationDetails.add(newMarketScanRow.getInstrumentSpecificationDetails());
				// log.info("new="+newMarketScanRow.getInstrumentSpecificationDetails().getInstrumentSpecification().getSymbol()+" at #"+newMarketScanRow.getRanking());
			}
			for (MarketScanRow removedMarketScanRow : removedMarketScanRows) {
				removedOrShiftedInstrumentSpecificationDetails.add(removedMarketScanRow.getInstrumentSpecificationDetails());
				// log.info("removed="+removedMarketScanRow.getInstrumentSpecificationDetails().getInstrumentSpecification().getSymbol()+" from #"+removedMarketScanRow.getRanking());
			}

			Set<InstrumentSpecificationDetails> newInstrumentSpecificationDetails = new HashSet<InstrumentSpecificationDetails>();// not
			// just
			// shifted
			newInstrumentSpecificationDetails.addAll(newOrShiftedInstrumentSpecificationDetails);
			newInstrumentSpecificationDetails.removeAll(removedOrShiftedInstrumentSpecificationDetails);

			Set<InstrumentSpecificationDetails> removedInstrumentSpecificationDetails = new HashSet<InstrumentSpecificationDetails>();// not
			// just
			// shifted
			removedInstrumentSpecificationDetails.addAll(removedOrShiftedInstrumentSpecificationDetails);
			removedInstrumentSpecificationDetails.removeAll(newOrShiftedInstrumentSpecificationDetails);

			Set<InstrumentSpecificationDetails> shiftedInstrumentSpecificationDetails = new HashSet<InstrumentSpecificationDetails>();
			shiftedInstrumentSpecificationDetails.addAll(removedOrShiftedInstrumentSpecificationDetails);
			shiftedInstrumentSpecificationDetails.addAll(newOrShiftedInstrumentSpecificationDetails);
			shiftedInstrumentSpecificationDetails.removeAll(newInstrumentSpecificationDetails);
			shiftedInstrumentSpecificationDetails.removeAll(removedInstrumentSpecificationDetails);

			List<Triple<InstrumentSpecificationDetails, Integer, Integer>> pendingShifts = new ArrayList<Triple<InstrumentSpecificationDetails, Integer, Integer>>();

//			for (MarketScanRow prevRow : prevRows) {
			for(int i=0;i<prevRows.size();i++){
				MarketScanRow prevRow=prevRows.get(i);	
				InstrumentSpecificationDetails instrumentSpecificationDetails = prevRow.getInstrumentSpecificationDetails();
				if (removedInstrumentSpecificationDetails.contains(instrumentSpecificationDetails)) {
					int removedFrom = i;//prevRow.getRanking();
					int amplitude = numberOfInstruments - removedFrom;
					double magnitude = ((double) amplitude / (double) numberOfInstruments);
					if (magnitude >= magnitudeRangeStart && magnitude <= magnitudeRangeEnd) {
						MarketScanChange removedChange = new MarketScanChange();
						removedChange.setInstrumentSpecificationDetails(instrumentSpecificationDetails);
						removedChange.setCurrentPosition(removedFrom);
						removedChange.setScanCriteria(newScan.getScanCriteria());
						removedChange.setTimeStamp(newScan.getTimeStamp());
						removedChange.setPreviousScanTime(previousScan.getTimeStamp());
						removedChange.setNumberOfInstruments(numberOfInstruments);
						removedChange.setChangeInterval(changeInterval);
						removedChange.setAmplitude(amplitude);
						removedChange.setMagnitude(magnitude);
						removedChange.setChangeType(ChangeType.INSTRUMENT_REMOVED);
						listener.eventFired(removedChange);
					}
				} else if (shiftedInstrumentSpecificationDetails.contains(instrumentSpecificationDetails)) {
					int shiftedFrom = i;//prevRow.getRanking();
					boolean foundSpec = false;
					for (Triple<InstrumentSpecificationDetails, Integer, Integer> pendingShift : pendingShifts) {
						if (pendingShift.getObject1().equals(instrumentSpecificationDetails)) {
							pendingShift.setObject2(shiftedFrom);
							if (pendingShift.getObject3() != null) {
								int shiftedTo = pendingShift.getObject3();
								if (shiftedFrom > shiftedTo) {
									int amplitude = shiftedFrom - shiftedTo;
									double magnitude = ((double) amplitude / (double) numberOfInstruments);
									if (magnitude >= magnitudeRangeStart && magnitude <= magnitudeRangeEnd) {
										MarketScanChange movedUpChange = new MarketScanChange();
										movedUpChange.setInstrumentSpecificationDetails(instrumentSpecificationDetails);
										movedUpChange.setPreviousPosition(shiftedFrom);
										movedUpChange.setCurrentPosition(shiftedTo);
										movedUpChange.setScanCriteria(newScan.getScanCriteria());
										movedUpChange.setTimeStamp(newScan.getTimeStamp());
										movedUpChange.setPreviousScanTime(previousScan.getTimeStamp());
										movedUpChange.setNumberOfInstruments(numberOfInstruments);
										movedUpChange.setChangeInterval(changeInterval);
										movedUpChange.setAmplitude(amplitude);
										movedUpChange.setMagnitude(magnitude);
										movedUpChange.setChangeType(ChangeType.INSTRUMENT_MOVED_UP);
										listener.eventFired(movedUpChange);
									}
								} else {
									int amplitude = shiftedTo - shiftedFrom;
									double magnitude = ((double) amplitude / (double) numberOfInstruments);
									if (magnitude >= magnitudeRangeStart && magnitude <= magnitudeRangeEnd) {
										MarketScanChange movedDownChange = new MarketScanChange();
										movedDownChange.setInstrumentSpecificationDetails(instrumentSpecificationDetails);
										movedDownChange.setPreviousPosition(shiftedFrom);
										movedDownChange.setCurrentPosition(shiftedTo);
										movedDownChange.setScanCriteria(newScan.getScanCriteria());
										movedDownChange.setTimeStamp(newScan.getTimeStamp());
										movedDownChange.setPreviousScanTime(previousScan.getTimeStamp());
										movedDownChange.setNumberOfInstruments(numberOfInstruments);
										movedDownChange.setChangeInterval(changeInterval);
										movedDownChange.setAmplitude(amplitude);
										movedDownChange.setMagnitude(magnitude);
										movedDownChange.setChangeType(ChangeType.INSTRUMENT_MOVED_DOWN);
										listener.eventFired(movedDownChange);
									}
								}
								pendingShifts.remove(pendingShift);
							}
							foundSpec = true;
							break;
						}
					}
					if (!foundSpec) {
						Triple<InstrumentSpecificationDetails, Integer, Integer> pendingShift = new Triple<InstrumentSpecificationDetails, Integer, Integer>(instrumentSpecificationDetails, shiftedFrom, null);
						pendingShifts.add(pendingShift);
					}
				}
			}

//			for (MarketScanRow newRow : newRows) {
			for(int i=0;i<newRows.size();i++){
				MarketScanRow newRow=newRows.get(i);		
				InstrumentSpecificationDetails instrumentSpecificationDetails = newRow.getInstrumentSpecificationDetails();
				if (newInstrumentSpecificationDetails.contains(instrumentSpecificationDetails)) {
					int addedTo = i;//newRow.getRanking();
					int amplitude = numberOfInstruments - addedTo;
					double magnitude = ((double) amplitude / (double) numberOfInstruments);
					if (magnitude >= magnitudeRangeStart && magnitude <= magnitudeRangeEnd) {
						MarketScanChange addedChange = new MarketScanChange();
						addedChange.setInstrumentSpecificationDetails(instrumentSpecificationDetails);
						addedChange.setCurrentPosition(addedTo);
						addedChange.setScanCriteria(newScan.getScanCriteria());
						addedChange.setTimeStamp(newScan.getTimeStamp());
						addedChange.setPreviousScanTime(previousScan.getTimeStamp());
						addedChange.setNumberOfInstruments(numberOfInstruments);
						addedChange.setChangeInterval(changeInterval);
						addedChange.setAmplitude(amplitude);
						addedChange.setMagnitude(magnitude);
						addedChange.setChangeType(ChangeType.INSTRUMENT_ADDED);
						listener.eventFired(addedChange);
					}
				} else if (shiftedInstrumentSpecificationDetails.contains(instrumentSpecificationDetails)) {
					int shiftedTo = i;//newRow.getRanking();
					boolean foundSpec = false;
					for (Triple<InstrumentSpecificationDetails, Integer, Integer> pendingShift : pendingShifts) {
						if (pendingShift.getObject1().equals(instrumentSpecificationDetails)) {
							pendingShift.setObject3(shiftedTo);
							if (pendingShift.getObject2() != null) {
								int shiftedFrom = pendingShift.getObject2();
								if (shiftedFrom > shiftedTo) {
									int amplitude = shiftedFrom - shiftedTo;
									double magnitude = ((double) amplitude / (double) numberOfInstruments);
									if (magnitude >= magnitudeRangeStart && magnitude <= magnitudeRangeEnd) {
										MarketScanChange movedUpChange = new MarketScanChange();
										movedUpChange.setInstrumentSpecificationDetails(instrumentSpecificationDetails);
										movedUpChange.setPreviousPosition(shiftedFrom);
										movedUpChange.setCurrentPosition(shiftedTo);
										movedUpChange.setScanCriteria(newScan.getScanCriteria());
										movedUpChange.setTimeStamp(newScan.getTimeStamp());
										movedUpChange.setPreviousScanTime(previousScan.getTimeStamp());
										movedUpChange.setNumberOfInstruments(numberOfInstruments);
										movedUpChange.setChangeInterval(changeInterval);
										movedUpChange.setAmplitude(amplitude);
										movedUpChange.setMagnitude(magnitude);
										movedUpChange.setChangeType(ChangeType.INSTRUMENT_MOVED_UP);
										listener.eventFired(movedUpChange);
									}
								} else {
									int amplitude = shiftedTo - shiftedFrom;
									double magnitude = ((double) amplitude / (double) numberOfInstruments);
									if (magnitude >= magnitudeRangeStart && magnitude <= magnitudeRangeEnd) {
										MarketScanChange movedDownChange = new MarketScanChange();
										movedDownChange.setInstrumentSpecificationDetails(instrumentSpecificationDetails);
										movedDownChange.setPreviousPosition(shiftedFrom);
										movedDownChange.setCurrentPosition(shiftedTo);
										movedDownChange.setScanCriteria(newScan.getScanCriteria());
										movedDownChange.setTimeStamp(newScan.getTimeStamp());
										movedDownChange.setPreviousScanTime(previousScan.getTimeStamp());
										movedDownChange.setNumberOfInstruments(numberOfInstruments);
										movedDownChange.setChangeInterval(changeInterval);
										movedDownChange.setAmplitude(amplitude);
										movedDownChange.setMagnitude(magnitude);
										movedDownChange.setChangeType(ChangeType.INSTRUMENT_MOVED_DOWN);
										listener.eventFired(movedDownChange);
									}
								}
								pendingShifts.remove(pendingShift);
							}
							foundSpec = true;
							break;
						}
					}
					if (!foundSpec) {
						Triple<InstrumentSpecificationDetails, Integer, Integer> pendingShift = new Triple<InstrumentSpecificationDetails, Integer, Integer>(instrumentSpecificationDetails, null, shiftedTo);
						pendingShifts.add(pendingShift);
					}
				}
			}
		}
	}
	/**
	 * returns the associated magnitudeRangeStart(double)
	 * @return
	 */
	public double getMagnitudeRangeStart() {
		return magnitudeRangeStart;
	}
	/**
	 * sets the associated magnitudeRangeStart(double) with the given magnitudeRangeStart(double)
	 * @param magnitudeRangeStart
	 */
	public void setMagnitudeRangeStart(double magnitudeRangeStart) {
		this.magnitudeRangeStart = magnitudeRangeStart;
	}
	/**
	 * returns the associated magnitudeRangeEnd(double)
	 * @return
	 */
	public double getMagnitudeRangeEnd() {
		return magnitudeRangeEnd;
	}
	/**
	 * sets the associated magnitudeRangeEnd(double) with the given magnitudeRangeEnd(double)
	 * @param magnitudeRangeEnd
	 */
	public void setMagnitudeRangeEnd(double magnitudeRangeEnd) {
		this.magnitudeRangeEnd = magnitudeRangeEnd;
	}
}
